#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/epoll.h>

#define BACKLOG 6

#define MAX_CLIENT_NUM  1024

static int (*client_open)(int fd, void **pptr);
static int (*client_read)(int *fd, void *ptr);


static int create_socket(int port)
{
    int ret, fd;

    struct sockaddr_in server = {0};
    server.sin_family = AF_INET;
    server.sin_addr.s_addr = htonl(INADDR_ANY);
    server.sin_port = htons(port);

    fd = socket(AF_INET, SOCK_STREAM, 0);
    if (fd < 0) {
        printf("socket: failed!\n");
        return -1;
    }

    int out = 1;
    setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &out, sizeof(out));

    ret = bind(fd, (struct sockaddr *)&server, sizeof(server));
    if (ret < 0) {
        printf("bind: failed!\n");
        close(fd);
        return ret;
    }

    ret = listen(fd, BACKLOG);
    if (ret < 0) {
        printf("bind: failed!\n");
        close(fd);
        return ret;
    }

    return fd;
}

static int epoll_socket(int sfd)
{
    int epfd;
    struct epoll_event ev, evset[MAX_CLIENT_NUM];

    if (sfd < 0) {
        return sfd;
    }

    epfd = epoll_create(1);

    ev.events = EPOLLIN;
    ev.data.ptr = &sfd;
    if (epoll_ctl(epfd, EPOLL_CTL_ADD, sfd, &ev) < 0) {
        printf("epoll add: failed!\n");
        return -1;
    }

    for (;;) {
        // epoll wait
        int num = epoll_wait(epfd, evset, MAX_CLIENT_NUM, -1);
        if (num < 0) {
            printf("epoll wait error!\n");
            return -1;
        }
        // got readable socket
        for (int i = 0; i < num; i++) {
            // printf("socket readable: %d/%d\n", i, num);
            if (evset[i].data.ptr == &sfd) { // new connection
#if 0
                struct sockaddr_in client = {0};
                socklen_t addrlen = sizeof(client);
                int cfd = accept(sfd, (struct sockaddr *)&client, &addrlen);
#else
                int cfd = accept(sfd, NULL, NULL);
#endif
                if (cfd < 0) {
                    printf("accept: failed!\n");
                    return -1;
                }

                void *data = NULL;
                if (client_open(cfd, &data) < 0) {
                    close(cfd);
                    continue;
                }

                ev.events = EPOLLIN;
                ev.data.ptr = data;
                if (epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &ev) < 0) {
                    printf("epoll add: failed!\n");
                    return -1;
                }
            } else { // client readable
                int cfd;
                if (client_read(&cfd, evset[i].data.ptr) < 0) {
                    epoll_ctl(epfd, EPOLL_CTL_DEL, cfd, NULL);
                    evset[i].data.ptr = NULL;
                    close(cfd);
                }
            }
        }
    }
}

int launch_server(int port, int (*cli_open)(int, void **), int (*cli_read)(int *, void *))
{
    client_open = cli_open;
    client_read = cli_read;
    return epoll_socket(create_socket(port));
}
